/*
 * CCVisu is a tool for visual graph clustering
 * and general force-directed graph layout.
 * This file is part of CCVisu.
 *
 * Copyright (C) 2005-2012  Dirk Beyer
 *
 * CCVisu is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * CCVisu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with CCVisu; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please find the GNU Lesser General Public License in file
 * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt
 *
 * Dirk Beyer    (firstname.lastname@uni-passau.de)
 * University of Passau, Bavaria, Germany
 */
package org.sosy_lab.ccvisu.ui.controlpanel;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.ListCellRenderer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.sosy_lab.ccvisu.Options;
import org.sosy_lab.ccvisu.Options.OptionsEnum;
import org.sosy_lab.ccvisu.clustering.ClustererBase;
import org.sosy_lab.ccvisu.clustering.ClustererMinDist;
import org.sosy_lab.ccvisu.clustering.ClustererMinDistPerc;
import org.sosy_lab.ccvisu.clustering.interfaces.Clusterer;
import org.sosy_lab.ccvisu.graph.GraphData;
import org.sosy_lab.ccvisu.ui.StopTaskButton;
import org.sosy_lab.util.interfaces.WorkerManager;

import com.google.common.base.Preconditions;

/**
 * User interface for the graph clustering.
 */
@SuppressWarnings("serial")
public class ToolPanelClusterer extends ControlPanel {

  private static final String TOOLTIP_RUN_CLUSTERING = "<html>Agglomerative clustering: initially, every node is in its own cluster; successive merge clusters "
      + "<br />until there is no other cluster to merge with within a certain distance (measured from the barycenters) "
      + "<br />or the least number of partitions has already been reached.</html>";

  private static final char   STOP_MNEMONIC         = 'S';
  private static final char   RUN_MNEMONIC          = 'R';

  private static final String CLUSTERER_THREAD_NAME = "Clusterer";
  private static final int    DEFAULT_MIN_DIST      = 40;

  private JComboBox<Class<? extends Clusterer>> clustererClassComboBox = new JComboBox<>();
  private JSpinner            numOfClustersSpinner  = new JSpinner();
  private JSlider             minDistanceSlider     = new JSlider(0, 100);
  private JLabel              minDistanceValueLabel = new JLabel();
  private JButton             runButton             = new JButton("Run clusterer");
  private StopTaskButton      stopTaskButton;

  private final Options       options;
  private final GraphData     graph;
  private final WorkerManager workerManager;

  public ToolPanelClusterer(Options options, WorkerManager workerManager) {
    Preconditions.checkNotNull(options);
    Preconditions.checkNotNull(options.graph);
    Preconditions.checkNotNull(workerManager);

    this.options = options;
    this.graph = options.graph;
    this.workerManager = workerManager;

    initComponents();
  }

  private void initComponents() {
    setLayout(new BorderLayout());
    setMinimumSize(new Dimension(500, 150));

    JPanel mainPanel = new JPanel();
    mainPanel.setLayout(new GridBagLayout());
    add(mainPanel, BorderLayout.CENTER);

    JPanel minDistancePanel = new JPanel(new BorderLayout());
    minDistanceSlider.setValue(DEFAULT_MIN_DIST);
    minDistanceSlider.setPaintTicks(false);
    minDistanceSlider.setPaintLabels(false);
    minDistancePanel.add(minDistanceSlider, BorderLayout.CENTER);
    minDistancePanel.add(minDistanceValueLabel, BorderLayout.EAST);

    addOptionControls(mainPanel, "Clusterer class:", clustererClassComboBox);
    addOptionControls(mainPanel, "Minimal cluster distance (%):", minDistancePanel);
    addOptionControls(mainPanel, "Number of partitions (at least):", numOfClustersSpinner);

    JPanel controlPanel = new JPanel();
    stopTaskButton = new StopTaskButton(workerManager);
    stopTaskButton.setMnemonic(STOP_MNEMONIC);
    runButton.setMnemonic(RUN_MNEMONIC);
    controlPanel.add(runButton);
    controlPanel.add(stopTaskButton);
    add(controlPanel, BorderLayout.SOUTH);

    registerAvailableClusterer();
    registerListeners();

    runButton.setToolTipText(TOOLTIP_RUN_CLUSTERING);
  }

  /**
   * Register available clusterer.
   */
  private void registerAvailableClusterer() {
    clustererClassComboBox.addItem(ClustererMinDistPerc.class);
    clustererClassComboBox.addItem(ClustererMinDist.class);
    clustererClassComboBox.setRenderer(new ClustererCellRenderer());

    clustererClassComboBox.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent arg0) {
        minDistanceSlider.setEnabled(clustererClassComboBox.getSelectedItem() == ClustererMinDistPerc.class);
      }
    });
  }

  /**
   * Register action handlers / event listeners.
   */
  private void registerListeners() {
    minDistanceSlider.addChangeListener(new ChangeListener() {
      @Override
      public void stateChanged(ChangeEvent pE) {
        minDistanceValueLabel.setText(String.format("%d", minDistanceSlider.getValue()));
      }
    });

    runButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent pE) {
        ClustererBase clusterer = createParameterizedClusterer();
        graph.clearGroups();
        workerManager.addAndRunTask(clusterer, CLUSTERER_THREAD_NAME);
      }
    });
  }

  /**
   * @return Instance of the chosen clusterer.
   */
  protected ClustererBase createParameterizedClusterer() {
    int numOfclusters = (Integer) numOfClustersSpinner.getValue();

    double minDistanceFract = minDistanceSlider.getValue() / 100.0;
    double mergeDistanceFract = options.getOption(OptionsEnum.clusterMergeDistancePercent).getFloat();

    Class<?> clustererClass = (Class<?>) clustererClassComboBox.getSelectedItem();
    if (clustererClass == ClustererMinDist.class) {
      return new ClustererMinDist(graph, numOfclusters);
    } else if (clustererClass == ClustererMinDistPerc.class) {
      return new ClustererMinDistPerc(graph, numOfclusters, mergeDistanceFract, minDistanceFract);
    } else {
      throw new RuntimeException("No valid clusterer selected!");
    }
  }

  /**
   * Render the class name of a clusterer.
   */
  private static class ClustererCellRenderer extends JLabel implements ListCellRenderer<Class<? extends Clusterer>> {
    @Override
    public Component getListCellRendererComponent(JList<? extends Class<? extends Clusterer>> list,
        Class<? extends Clusterer> value, int index, boolean isSelected, boolean cellHasFocus) {
      setText(value.getSimpleName());
      return this;
    }
  }

}
